package com.study.el;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.*;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.expression.*;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 参考 https://blog.csdn.net/wb1046329430/article/details/121563724
 * <p>
 * 1、什么是 SpringEL：Spring3 中引入了 Spring 表达式语言 — Spring EL，SpEL 是一种强大，简洁的装配 Bean 的方式，它可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中，更可以调用 JDK 中提供的静态常量，获取外部 Properties 文件中的的配置。
 * 2、为什么要使用 SpringEL：我们平常通过配置文件或注解注入的 Bean，其实都可以称为静态性注入，试想一下，如果我的 Bean A 中有变量 A，它的值需要根据 Bean B 的 B 变量为参考，在这个场景下静态注入就显得非常无力，而 Spring3 增加的 Spring EL 就可以完全满足这种需求，而且还可以对不同 Bean 的字段进行计算再进行赋值，功能非常强大。
 * 3、如何使用 SpringEL：SpringEL 从名字来看就能看出，和 EL 是有关系的，Spring EL 的使用和 EL 表达式的使用非常相似，EL 表达式在 JSP 页面更方便的获取后台中的值，而 Spring EL 就是为了更方便获取 Spring 容器中的 Bean 的值，EL 使用${}，而 Spring EL 使用#{}进行表达式的声明。
 *
 * @author doveylovey
 * @version v1.0.0
 * @email 1135782208@qq.com
 * @date 2023年02月10日
 */
public class SpringElTests01 {
    @Test
    public void testSpringEl01() {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringElConfig.class);
        TestSpringEL testSpringEL = context.getBean(TestSpringEL.class);
        System.out.println(testSpringEL);
    }

    @Test
    public void testSpringEl02() {
        String expressionString = "'hello world'.toUpperCase().substring(1,5)";
        // 指定 SpelExpressionParser 解析器实现类
        ExpressionParser parser = new SpelExpressionParser();
        // 解析表达式
        Expression expression = parser.parseExpression(expressionString);
        System.out.println(expression.getValue());
    }

    @Test
    public void testSpringEl03() {
        String expressionString = "'hello world'.toUpperCase().substring(#start, #end)";
        // 指定 SpelExpressionParser 解析器实现类
        ExpressionParser parser = new SpelExpressionParser();
        // 解析表达式
        Expression expression = parser.parseExpression(expressionString);
        // 设置对象模型基础
        EvaluationContext context = new StandardEvaluationContext();
        context.setVariable("start", 1);
        context.setVariable("end", 5);
        System.out.println(expression.getValue(context));
    }

    @Test
    public void testSpringEl04() {
        ExpressionParser parser = new SpelExpressionParser();

        Expression expression01 = parser.parseExpression("1+2+4+5/2");
        System.out.println("四则运算：" + expression01.getValue(Integer.class));

        Expression expression02 = parser.parseExpression("3%2");
        // Expression expression02 = parser.parseExpression("3 MOD 2");
        System.out.println("模：" + expression02.getValue(Integer.class));

        Expression expression03 = parser.parseExpression("2 ^ 2");
        System.out.println("幂：" + expression03.getValue(Integer.class));

        Expression expression04 = parser.parseExpression("3 / 2");
        System.out.println("除法：" + expression04.getValue(Integer.class));

        Expression expression05 = parser.parseExpression("1 == 2");
        // Expression expression05 = parser.parseExpression("1 EQ 2");
        System.out.println("等于：" + expression05.getValue(Boolean.class));

        Expression expression06 = parser.parseExpression("1 != 2");
        // Expression expression06 = parser.parseExpression("1 NE 2");
        System.out.println("不等于：" + expression06.getValue(Boolean.class));

        Expression expression07 = parser.parseExpression("1 > 2");
        // Expression expression07 = parser.parseExpression("1 GT 2");
        System.out.println("大于：" + expression07.getValue(Boolean.class));

        Expression expression08 = parser.parseExpression("1 >= 2");
        // Expression expression08 = parser.parseExpression("1 GE 2");
        System.out.println("大于等于：" + expression08.getValue(Boolean.class));

        Expression expression09 = parser.parseExpression("1 BETWEEN {5,20}");
        System.out.println("区间：" + expression09.getValue(Boolean.class));

        Expression expression10 = parser.parseExpression("1 == 1 AND 2 == 2");
        System.out.println("与运算：" + expression10.getValue(Boolean.class));

        Expression expression11 = parser.parseExpression("1 == 1 OR 2 == 2");
        System.out.println("或运算：" + expression11.getValue(Boolean.class));

        Expression expression12 = parser.parseExpression("1 > 0 ? '正数' : '负数'");
        System.out.println("三目运算：" + expression12.getValue(String.class));
    }

    @Test
    public void testSpringEl05() {
        // 设置文字模板，其中#{}表示表达式的起止，#user是表达式字符串，表示引用一个变量。
        String template = "#{#user}，早上好";
        // 创建表达式解析器。
        ExpressionParser parser = new SpelExpressionParser();
        // 通过 EvaluationContext.setVariable() 可以在上下文中设定变量。
        EvaluationContext context = new StandardEvaluationContext();
        context.setVariable("user", "黎明");
        // 解析表达式，如果表达式是一个模板表达式，需要为解析传入模板解析器上下文。
        Expression expression = parser.parseExpression(template, new TemplateParserContext());
        // 使用 Expression.getValue() 获取表达式的值，这里传入了 Evaluation 上下文，第二个参数是类型参数，表示返回值的类型。
        System.out.println(expression.getValue(context, String.class));
    }

    @Test
    public void testSpringEl06() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode root = mapper.createObjectNode();
        root.put("pubTime", "11111");
        ObjectNode supplier = root.putObject("supplier");
        supplier.put("sourceType", "TestSourceType");
        supplier.put("comFrom", "QX");
        System.out.println("JSON字符串：" + mapper.writeValueAsString(root));

        String sourceType00 = root.get("supplier").path("sourceType").asText();
        System.out.println("sourceType值：" + sourceType00);

        Expression expression01 = ExpressionUtils.getExpression("get('supplier').path('sourceType').asText()");
        String sourceType01 = Objects.requireNonNull(expression01).getValue(root, String.class);
        System.out.println("sourceType值：" + sourceType01);

        Expression expression02 = ExpressionUtils.getExpression("get('supplier').path('sourceType').asText()");
        String sourceType02 = Objects.requireNonNull(expression02).getValue(root, String.class);
        System.out.println("sourceType值：" + sourceType02);
    }
}

@Configuration
@ComponentScan("com.study.el")
class SpringElConfig {
}

@Component
class TestConstant {
    public static final String STR = "测试SpEL";
    public String nickname = "一线大码";
    public String name = "笑傲江湖";
    public int num = 5;
    public List<String> testList = Arrays.asList("aaa", "bbb", "ccc");
    public Map<String, String> testMap = new HashMap<String, String>() {{
        put("aaa", "元宇宙算法");
        put("hello", "world");
    }};
    public List<City> cityList = new ArrayList<City>() {{
        add(new City("aaa", 500));
        add(new City("bbb", 600));
        add(new City("ccc", 1000));
        add(new City("ddd", 1000));
        add(new City("eee", 2000));
        add(new City("fff", 3000));
    }};

    public String showProperty() {
        return "Hello";
    }

    public String showProperty(String str) {
        return "Hello " + str + "!";
    }
}

@Data
@AllArgsConstructor
class City {
    private String name;
    private long population;
}

@ToString
@Component
class TestSpringEL {
    /**
     * 注入简单值，输出num为5
     */
    @Value("#{5}")
    private Integer num;

    @Value("#{'rain'.toUpperCase()}")
    private String name;

    // 注入bean，访问属性和方法

    /**
     * 注入ID为testConstant的Bean
     */
    @Value("#{testConstant}")
    private TestConstant testConstant;

    /**
     * 注入ID为testConstant的Bean中的STR常量/变量
     */
    @Value("#{testConstant.STR}")
    private String str;

    /**
     * 调用无参方法
     */
    @Value("#{testConstant.showProperty()}")
    private String method1;

    /**
     * 调用有参方法，接收字符串
     */
    @Value("#{testConstant.showProperty('World')}")
    private String method2;

    /**
     * 方法返回的String为大写
     */
    @Value("#{testConstant.showProperty().toUpperCase()}")
    private String method3;

    /**
     * 使用method3这种方式，如果showProperty返回为null，将会抛出NullPointerException，可以使用以下方式避免。
     * 使用?.符号表示如果左边的值为null，将不执行右边方法
     */
    @Value("#{testConstant.showProperty()?.toUpperCase()}")
    private String method4;

    // 注入JDK中的工具类常量或调用工具类的方法

    /**
     * 获取Math的PI常量
     */
    @Value("#{T(java.lang.Math).PI}")
    private double pi;

    /**
     * 调用random方法获取返回值
     */
    @Value("#{T(java.lang.Math).random()}")
    private double random;

    /**
     * 获取文件路径符号
     */
    @Value("#{T(java.io.File).separator}")
    private String separator;

    // 使用SpringEL进行运算及逻辑操作

    /**
     * 拼接字符串
     */
    @Value("#{testConstant.nickname + ' ' + testConstant.name}")
    private String concatString;

    /**
     * 对数字类型进行运算
     */
    @Value("#{ 3 * T(java.lang.Math).PI + testConstant.num}")
    private double operation;

    /**
     * 进行逻辑运算
     */
    @Value("#{testConstant.num > 100 and testConstant.num <= 200}")
    private boolean logicOperation;

    /**
     * 进行或非逻辑操作
     */
    @Value("#{not(testConstant.num == 100 or testConstant.num <= 200)}")
    private Boolean logicOperation2;

    /**
     * 使用三元运算符
     */
    @Value("#{testConstant.num > 100 ? testConstant.num : testConstant.num + 100}")
    private Integer logicOperation3;

    // SpringEL使用正则表达式

    /**
     * 验证是否邮箱地址正则表达式
     */
    @Value("#{testConstant.STR matches '\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+'}")
    private boolean regularExpression;

    // TestConstant类中有名为testList的List变量，和名为testMap的Map变量

    /**
     * 获取下标为0的元素
     */
    @Value("#{testConstant.testList[0]}")
    private String firstStr;

    /**
     * 获取下标为0元素的大写形式
     */
    @Value("#{testConstant.testList[0]?.toUpperCase()}")
    private String upperFirstStr;

    /**
     * 获取map中key为hello的value
     */
    @Value("#{testConstant.testMap['hello']}")
    private String mapValue;

    /**
     * 根据testList下标为0元素作为key获取testMap的value
     */
    @Value("#{testConstant.testMap[testConstant.testList[0]]}")
    private String mapValueByTestList;

    // 声明City类，有population（人口）属性。testConstant拥有名为cityList的City类List集合

    /**
     * 过滤testConstant中cityList集合population属性大于1000的全部数据注入到本属性
     */
    @Value("#{testConstant.cityList.?[population > 1000]}")
    private List<City> cityList;

    /**
     * 过滤testConstant中cityList集合population属性等于1000的第一条数据注入到本属性
     */
    @Value("#{testConstant.cityList.^[population == 1000]}")
    private City city;

    /**
     * 过滤testConstant中cityList集合population属性小于1000的最后一条数据注入到本属性
     */
    @Value("#{testConstant.cityList.$[population < 1000]}")
    private City city2;

    /*
     * 首先为city增加name属性，代表城市的名称
     */

    /**
     * 假如我们在过滤城市集合后只想保留城市的名称，可以使用如下方式进行投影
     */
    @Value("#{testConstant.cityList.?[population > 1000].![name]}")
    private List<String> cityName;
}

class ExpressionUtils {
    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
    private static final Map<String, Expression> EXPRESSION_MAP = new ConcurrentHashMap<>();

    /**
     * 获取解析后的表达式
     *
     * @param expression EL表达式字符串
     * @return 解析后的表达式，如果之前已经解析过，则返回缓存的表达式
     */
    public static Expression getExpression(String expression) {
        Expression result = null;
        if (StringUtils.hasText(expression)) {
            result = EXPRESSION_MAP.computeIfAbsent(expression.trim(), EXPRESSION_PARSER::parseExpression);
        }
        return result;
    }
}
